/*
 * pn.cpp
 *
 *  Created on: 25.04.2012
 *      Author: g.kruglov
 */

#include "pn.h"
#include <util/delay.h>
#include <stdarg.h>
#include "delay_util.h"
#include "string.h"
#include "card.h"
#include "uart_soft.h"
#include "main.h"
//#include "ccid.h"
#include "led.h"

PN_t Pn;


// ============================= Implementation ================================
void PN_t::Task() {
    if (DelayElapsed(&ITimer, 702)) {
//        klPrintf("#\r");
        if (Card.State != csCardOk) {
            if (CardIsAppeared()) {
                Card.State = csCardOk;
                klPrintf("#\r");
                CardIn = true;
            }
        }
        else {
            if (!CheckIfCardStillNear()) {
                Card.State = csCardOut;
//                klPrintf("Removed\r");
                CardIn = false;
            }
        } // if Card is ok
    } // if delay
}


void PN_t::LedTask() {
    if (CardIn) {
        Led.RedOff();
//        Led.BlueOn();
//        if (DelayElapsed(&LedTimer, 300)) Led.BlueOn();
        if ((!DelayElapsed(&LedTimer, 3621)) && Led.BlueNeedToggle) {
                Led.BlueToggle();
                Led.CCidNeedBlink = false;
        } else {
            Led.BlueNeedToggle = false;
            Led.CCidNeedBlink = true;
            Led.BlueOn();
        }

    } else {
        Led.RedOn();
        Led.BlueNeedToggle = true;
//        DelayReset(&LedTimer);
        Led.BlueOff();
    }

}

void PN_t::Init() {
    PN_DDR  &= ~((1<<PN_MISO_PIN)|(1<<PN_IRQ_PIN));
    //PN_PORT |= (1<<PN_IRQ_PIN);
    PN_DDR  |= (1<<PN_NSS_PIN)|(1<<PN_MOSI_PIN)|(1<<PN_RESET_PIN)|(1<<PN_SCK_PIN);
    PN_RESET_LO();
    PN_NSS_HI();
    PN_SCK_LO();

    _delay_ms(4);   // Let the reset to act
    PN_RESET_HI();  // Remove reset
    _delay_ms(9);   // Allow PN to wakeup.

    // Initial commands
    Cmd(PN_CMD_GET_FIRMWARE_VERSION, 0);   // First Cmd will be discarded
    Cmd(PN_CMD_SAM_CONFIGURATION, 1, 0x01); // Disable SAM to calm PN: Normal mode, the SAM is not used
    Cmd(PN_CMD_RF_CONFIGURATION, 4, 0x05, 0x02, 0x01, 0x05);  // Setup RF config: One retry, ATR: once, PSL: once, Activation: once
}

// ================================ Card =======================================
/*
 * Check if new cards appeared
 */
bool PN_t::CardIsAppeared() {
    FieldOn();
    // Check if something is near
    // Max number of targets = 2, Baudrate: 106kbps type A
    if (Cmd(PN_CMD_IN_LIST_PASSIVE_TARGET, 2, 0x01, 0x00) != pnPktOk) { FieldOff(); return false; }
    if ((Buf[0] != PN_RPL_IN_LIST_PASSIVE_TARGET) or (Buf[1] == 0))   { FieldOff(); return false; } // Incorrect reply or nothing found
    // ==== Tag is found ====
    klPrintf("Tag: %A\r", &Buf[2], Length-2);
    Card.Data = &Buf[3];
    Card.DataLength = Length-3;
    Card.DetermineType();
    Card.ConstructATR();
    if (Card.IsMifare && (Card.Type == ctMifareUltralight)) {
        ReadMifareMemoryto(Metro.MemoryBuf);
        Card.MetroInfoDescrypted(Metro.MemoryBuf);
    }

    return true;
}

bool PN_t::CheckIfCardStillNear() {
        if (Card.IsMifare) {
            // Deselect, Select, Check
            if (Cmd(PN_CMD_IN_DESELECT, 1, 1) == pnPktOk) {
//                klPrintf("Mifare deselect: %u %X\r", i, Buf[1]);
                if (Cmd(PN_CMD_IN_SELECT, 1, 1) == pnPktOk) {
//                    klPrintf("Mifare select: %u %X\r", i, Buf[1]);
                    if (Buf[0] == PN_RPL_IN_SELECT) {
//                        klPrintf("Select: %X\r", Buf[1]);
                        if (Buf[1] == 0) {
                            return true;
                        }  // Selected successfully
                    } // if rpl select
                } // if pkt select
            } // if deselect
        }
    // Else nothing left near
    Cmd(PN_CMD_IN_RELEASE, 1, 1);
    FieldOff();
//    klPrintf("Nothing left near - FieldOff\r");
    return false;
}


/*
 * Read 64 bytes into MBuf.
 */

void PN_t::ReadMifareMemoryto(uint8_t *MBuf) {
    uint8_t page = 0;
//    klPrintf("Memory read.");
    for (uint8_t i=0; i<=16; i++) {
        if (Cmd(PN_CMD_IN_DATA_EXCHANGE, 3, 0x01, 0x30, i) == pnPktOk) {
            memcpy(&MBuf[page], &Buf[2], 4);
        } // if correct reply
//        klPrintf(".");
        page += 4;
    } // page
//    klPrintf("Ok!\r%A\r", MBuf, 64);
}
// ================================== PN =======================================
/*
 * Send Cmd to PN and get reply.
 * ALength is count of bytes to send excluding ACmd, i.e. ALength == count of arguments
 */
PN_PktRslt_t PN_t::Cmd(uint8_t ACmd, uint16_t ALength, ...) {
    Length = ALength + 1;
    Buf[0] = ACmd;
    if (ALength != 0) {
        va_list Arg;
        va_start(Arg, ALength);    // Set pointer to last argument
        for(uint32_t i=0; i<ALength; i++) Buf[i+1] = (uint8_t)va_arg(Arg, int);
        va_end(Arg);
    }
//    klPrintf("pn cmd: %A\r", Buf, Length);
    return WriteAndWaitData();
}


PN_PktRslt_t PN_t::WriteAndWaitReply(void) {
    WritePkt();
    // Now wait for ACK for about 27 ms
    _delay_us(11);
    uint16_t i=0;
    uint8_t l=0;
    while (!PN_REPLY_IS_READY()) {
        _delay_ms(1);
        if (l++ == 205) {
            Led.BlueToggle();
            l = 0;
        }
        // Check timeout, get out if fired
        if (++i > 128) {
//            klPrintf("wwr Cmd %X timeout\r", Buf[0]);
            return pnPktFail;
        }
    }
    // Some reply is ready
    PN_PktRslt_t Response = ReadPkt();
    return Response;
}

PN_PktRslt_t PN_t::WriteAndWaitData(void) {
    PN_PktRslt_t Response = WriteAndWaitReply();
    if (Response == pnPktACK) {    // ACK => proceed with getting reply packet
        _delay_us(11);

        uint16_t i=0;
        uint8_t l=0;
        while (!PN_REPLY_IS_READY()) {
            _delay_ms(1);
            if (l++ == 205) {
                Led.BlueToggle();
                l = 0;
            }

            // Check timeout, get out if fired
            if (++i > 421) {
//                klPrintf("wwd Cmd %X timeout\r", Buf[0]);
                return pnPktFail;
            }
        }
        // Some reply is ready
        Response = ReadPkt();
    } // if ACK
    return Response;
}


// ****************************************************************************
// * Transmits data from Buf of length Length                                 *
// * Always uses extended frame format - for simplicity                       *
// ****************************************************************************
void PN_t::WritePkt() {
    uint8_t DCS = PN_TFI_TRANSMIT;
    // Compute length bytes. +1 due to TFI byte
    uint8_t ILngHi = (uint8_t)(0x00FF & ((Length+1) >> 8));
    uint8_t ILngLo = (uint8_t)(0x00FF &  (Length+1));
    PN_NSS_LO();
    WriteReadByte(PN_PRE_DATA_WRITE);   // DW: following operation is writing (p. 42 of user manual)
    WriteReadByte(0x00);    // PREAMBLE
    WriteReadByte(0x00);    // }
    WriteReadByte(0xFF);    // } START CODE: 0x00 0xFF - Start of packet code
    WriteReadByte(0xFF);    // }
    WriteReadByte(0xFF);    // } Extended frame identifier
    WriteReadByte(ILngHi);  // Upper byte of Length
    WriteReadByte(ILngLo);  // Lower byte of Length
    WriteReadByte(-(ILngHi + ILngLo));     // LCS: Length checksum: (LCS+LEN) = 0
    WriteReadByte(PN_TFI_TRANSMIT); // TFI: frame identifier
    // Payload data
    for (uint32_t i=0; i<Length; i++) {
        WriteReadByte (Buf[i]);
        DCS += Buf[i];
    } // for
    WriteReadByte (-DCS);   // DCS: packet data checksum: (TFI + Cmd+ AData[0] + AData[1] +...+ AData[n] + DCS) = 0
    WriteReadByte (0x00);   // POSTAMBLE
    PN_NSS_HI();
}

PN_PktRslt_t PN_t::ReadPkt(void) {
    uint8_t tmp;
    PN_NSS_LO();
    WriteReadByte(PN_PRE_DATA_READ);    // Write a byte indicating that the following operation is reading
    // Skip all zero bytes
    do {
        tmp = WriteReadByte(0);
    } while (tmp == 0x00);

    // First non-zero byte must be 0xFF; get out if not
    if (tmp != 0xFF) {
        PN_NSS_HI();
        return (pnPktFail);
    }

    // Recieve next 3 bytes to determine frame type
    Buf[0] = WriteReadByte(0);
    Buf[1] = WriteReadByte(0);
    Buf[2] = WriteReadByte(0);

    uint8_t FCheckSum;
    // Check if ACK: 00_FF_00
    if ((Buf[0] == 0x00) && (Buf[1] == 0xFF) && (Buf[2] == 0x00)) {
        PN_NSS_HI();
        return (pnPktACK);
    }

    // Check if Error Frame: 01_FF_7F...
    else if ((Buf[0] == 0x01) && (Buf[1] == 0xFF) && (Buf[2] == 0x7F)) {
        Buf[3] = WriteReadByte(0); // must be 0x81
        Buf[4] = WriteReadByte(0); // must be 0x00
        PN_NSS_HI();
        // Check if syntax error
        if ((Buf[3] != 0x81) || (Buf[4] != 0x00)) return (pnPktFail);
        else return (pnPktErrFrame);

    }

    // Check if Extended Frame: FF_FF...
    else if ((Buf[0] == 0xFF) && (Buf[1] == 0xFF)) {
        // R[2] is LENm, already read
        Buf[3] = WriteReadByte(0);   // R[3] is LENl
        Buf[4] = WriteReadByte(0);   // R[4] is LCS
        Buf[5] = WriteReadByte(0);   // TFI
        // Check Length checksum & TFI
        FCheckSum = Buf[2] + Buf[3] + Buf[4];
        if ((FCheckSum != 0) || (Buf[5] != PN_TFI_RECIEVE)) {
            PN_NSS_HI();
            return (pnPktCRC);
        }
        // Calculate Big Length
        Length = Buf[2];
        Length <<= 8;
        Length += Buf[3];
        Length--; // as LEN includes TFI
    }

    // If normal frame
    else {
        // Buf[0] is LEN, Buf[1] is LCS, Buf[2] is TFI
        // Check Length checksum & TFI
        FCheckSum = Buf[0] + Buf[1];
        if ((FCheckSum != 0) || (Buf[2] != PN_TFI_RECIEVE)) {
            PN_NSS_HI();
            return (pnPktCRC);
        }
        // CRC ok
        Length = Buf[0] - 1;  // -1 as LEN includes TFI
    }
    // Receive remainder of packet, discarding received start of packet
    FCheckSum = PN_TFI_RECIEVE;
    for (uint32_t i=0; i<Length; i++) {
        Buf[i] = WriteReadByte(0);
        FCheckSum += Buf[i];
    }
    // Recieve DCS
    FCheckSum += WriteReadByte(0);
    // Check for FCheckSum == 0: TFI+Data[0]+Data[1]+...+DCS must be equal to 0
    if (FCheckSum != 0) {
        PN_NSS_HI();
        return (pnPktCRC);
    }
    // Recieve postamble
    WriteReadByte(0);
    PN_NSS_HI();
    // DEBUG
    return (pnPktOk);
}




uint8_t PN_t::WriteReadByte(uint8_t AByte) {
    uint8_t res = 0;
    for (uint8_t i=0; i<8; i++) {
        if (AByte & 0x01) PN_MOSI_HI();
        else PN_MOSI_LO();
        AByte >>= 1;
        _delay_us(45);
        PN_SCK_HI();
        _delay_us(45);
        res >>= 1;
        if(PN_MISO_IS_HI()) res |= 0x80;
        PN_SCK_LO();
    }
    return res;
}
